﻿using System;
using System.Collections.Generic;
using System.Configuration;
using PSE.ProcessManager.Common.Gateway;
using PSE.ProcessManager.Server.InteligenceModules;
//using PSE.ProcessManager.Common.Logging;
using System.ServiceModel;
using System.Threading;
using System.Data.Common;
using System.Data;
using PSE.ProcessManager.Server.Process;
using PSE.ProcessManager.Entity;
using PSE.ProcessManager.ResourceAccess;
using PSE.Framework.ErrorLogging;
using PSE.ProcessManager.Common.Process;
using PSE.Framework.Process;
using PSE.Framework.Configuration;

namespace PSE.ProcessManager.Server.InteligenceManagerInstance
{
    /// <summary>
    /// Responsável pelo gerenciamento e sustentação dos processos.
    /// </summary>
    public class InteligenceManager : AppDomainObject, IDisposable
    {
        #region Properties
        /// <summary>
        /// Dicionário com todos os clients conectados e os processos que eles estão ouvindo.
        /// </summary>
        private Dictionary<int, List<IListenerServiceCallback>> _connectedClients;

        /// <summary>
        /// Dicionário com todos os clients conectados e os processos que eles estão ouvindo.
        /// </summary>
        private Dictionary<MultipleProcessKey, IListenerServiceCallback> _connectedClientsToMultipleProcess;

        /// <summary>
        /// Gateway de comunicação com a camada Client.
        /// </summary>
        private ReceiverGateway _listenerGateway;
        /// <summary>
        /// Endereço do Listener do InteligenceManager.
        /// </summary>
        private const string _serviceAddress = "InteligenceManager";
        private const int _errorMessageId = 4;
        private const int _processFinalizedMessageId = 2;
        private const int _processFinalizedOnFailMessageId = 3;
        private const int _sucessOnConectMessageId = 6;

        private InteligenceModuleLoader _inteligenceModuleLoader;

        private IProcessManagerResourceAccess _resourceAccess;
        #endregion

        /// <summary>
        /// Impedir que o objeto saia da memoria durante a execução
        /// </summary>
        /// <returns></returns>
        public override object InitializeLifetimeService()
        {
            return null;
        }

        #region Contructor

        /// <summary>
        /// Construtor
        /// </summary>
        public InteligenceManager()
        { 
            _connectedClients = new Dictionary<int, List<IListenerServiceCallback>>();

            _connectedClientsToMultipleProcess = new Dictionary<MultipleProcessKey, IListenerServiceCallback>(new MultipleProcessKeyComparer());

            //try
            //{
            //Carrega o Inteligence Module referente a mensagem.
            _inteligenceModuleLoader = new InteligenceModuleLoader();

            _resourceAccess = ProcessManagerResourceAccessFactory.CreateProvider();
            //}
            //catch (Exception ex)
            //{
            //    Logger.Write(new Exception("Error initializing InteligenceModuleLoader", ex));
            //}
        }

        #endregion

        #region Initialize
        /// <summary>
        /// Inicia o InteligenceManager.
        /// </summary>
        internal bool Initialize(string[] args)
        {
            using (InteligenceManagerInstanceProcessManager process = InteligenceManagerInstanceProcessManager.GetInstance())
            {
                process.Initialize(args[0], new ParentProcessStartInfo(args));

                //Inicia o Gateway de comunicação.
                _listenerGateway = new ReceiverGateway();
                try
                {
                    //Define o evento que será disparado quando receber uma nova solicitação de conexão.
                    _listenerGateway.StartListener(_serviceAddress, Protocol.TCP);
                }
                catch (Exception ex)
                {
                    HandleException(new Exception("Error initializing Receiver Gateway.", ex));
                    return false;
                }

                process.Parent.ReleaseParent();

                //Define o evento que será disparado quando receber uma mensagem do Listener.
                _listenerGateway.OnReceiveMessage = new ServiceEvents.MessageReceived(InteligenceManager_OnReceivedMessage);
                //Define o delegate que será disparado quando receber uma nova requisição de conexão.
                _listenerGateway.OnConnect = new ServiceEvents.ClientConnected(InteligenceManager_OnConnect);
                //Define o delegate que será disparado quando houver uma desconexão do client.
                _listenerGateway.OnDisconnect = new ServiceEvents.ClientDisconnected(InteligenceManager_OnDisconnect);

                _listenerGateway.IsProcessRunning = new ServiceEvents.IsProcessRunning(InteligenceManager_IsProcessRunning);

                _listenerGateway.OnRetrieveLogList = new ServiceEvents.RetrievedLogList(InteligenceManager_OnRetrieveLogList);

                _listenerGateway.OnRetrieveLastProcessLog = new ServiceEvents.RetrievedLastProcessLog(InteligenceManager_OnRetrieveLastProcessLog);

                List<int> ids = _resourceAccess.RetrieveAutoStartProcesses();
                //if (ids.Count > 0)
                //    this.InitializeThreadPool(ids);
                foreach (int id in ids)
                {
                    this.LoadAndInitialize(id);
                }

                process.WaitForExit();

                return true;
            }
        }
        #endregion

        /// <summary>
        /// Verifica se o processo passado esta em execução.
        /// </summary>
        /// <param name="processId"></param>
        /// <returns></returns>
        public int InteligenceManager_IsProcessRunning(int processId)
        {
            int result = -1;

            try
            {
                BaseInteligenceModule inteligenceModule = _inteligenceModuleLoader.RetrieveInteligenceModuleFromSingleCache(processId);
                if (inteligenceModule != null && inteligenceModule.IsProcessRunning > -1)
                    result = inteligenceModule.IsProcessRunning;

                inteligenceModule = _inteligenceModuleLoader.RetrieveInteligenceModuleFromMultipleCache(processId);

                if (inteligenceModule != null && inteligenceModule.IsProcessRunning > -1)
                    result = inteligenceModule.IsProcessRunning;
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }

            return result;
        }

        public List<LogEntity> InteligenceManager_OnRetrieveLogList(int processId)
        {
            List<LogEntity> logList = new List<LogEntity>();

            try
            {
                return _resourceAccess.RetrieveLogList(processId);
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }

            return logList;
        }

        public LogEntity InteligenceManager_OnRetrieveLastProcessLog(int processId)
        {
            LogEntity logEntity = null;

            try
            {
                logEntity = _resourceAccess.RetrieveLastProcessLog(processId);
            }
            catch (Exception ex)
            {
                HandleException(ex);
                throw;
            }

            return logEntity;
        }

        #region AutoStart Feature

        /// <summary>
        /// Inicializa o ThreadPool
        /// </summary>
        //void InitializeThreadPool(List<int> ids)
        //{
        //    //TODO: Definir qual será o tamanho do pool
        //    int totalThreads = 4;
        //    int threadsExec = 0;

        //    AutoResetEvent[] autoEvents = new AutoResetEvent[totalThreads];

        //    foreach (int processId in ids)
        //    {
        //        //InteligenceManager objThread = new InteligenceManager();
        //        EventProcess eventProcess;

        //        if (threadsExec < totalThreads)
        //        {
        //            eventProcess.ProcessId = processId;
        //            autoEvents[threadsExec] = new AutoResetEvent(false);
        //            eventProcess.Event = autoEvents[threadsExec];

        //            //ThreadPool.QueueUserWorkItem(new WaitCallback(objThread.InitiateAutoStartProcess), eventProcess);
        //            ThreadPool.QueueUserWorkItem(new WaitCallback(this.InitiateAutoStartProcess), eventProcess);
        //            threadsExec++;
        //        }
        //        else
        //        {
        //            int processedThread = WaitHandle.WaitAny(autoEvents);
        //            eventProcess.ProcessId = processId;
        //            autoEvents[processedThread].Reset();
        //            eventProcess.Event = autoEvents[processedThread];
        //            //ThreadPool.QueueUserWorkItem(new WaitCallback(objThread.InitiateAutoStartProcess), eventProcess);
        //            ThreadPool.QueueUserWorkItem(new WaitCallback(this.InitiateAutoStartProcess), eventProcess);
        //        }
        //    }
        //}

        //public void InitiateAutoStartProcess(object stateInfo)
        //{
        //    try
        //    {
        //        EventProcess eventProcess = (EventProcess)stateInfo;
        //        int processId = eventProcess.ProcessId;
        //        AutoResetEvent autoEvent = eventProcess.Event;
        //        this.LoadAndInitialize(eventProcess.ProcessId);
        //        autoEvent.Set();
        //    }
        //    catch (Exception ex)
        //    {
        //    }
        //}

        internal void LoadAndInitialize(int processId)
        {
            //Caso o processo não exista, inicializa-o.
            BaseInteligenceModule inteligenceModule = null;

            try
            {
                //Recupera o InteligenceModule associado a mensagem.
                inteligenceModule = _inteligenceModuleLoader.LoadAndInitializeInteligenceModule(processId, string.Empty);
                
                if (inteligenceModule == null)
                    return;
                
                //Define o evento que será disparado ao receber mensagens deste inteligenceModule.
                inteligenceModule.OnReceiveMessage = new ServiceEvents.MessageReceived(OnReceivedMessageFromInteligenceModule);
            }
            catch (Exception ex)
            {
                HandleException(processId, new Exception("Error loading InteligenceModule", ex));
            }
        }

        //private struct EventProcess
        //{
        //    public int ProcessId;
        //    public AutoResetEvent Event;
        //}

        #endregion

        #region Connect - Disconnect
        /// <summary>
        /// Evento disparado ao receber uma requisição de conexão do Client.
        /// </summary>
        /// <param name="messageIds">List contendo as mensagens que o client deseja escutar</param>
        /// <param name="request">Instância do Channel de Callback do client.</param>
        void InteligenceManager_OnConnect(int processId, string clientIdentifier, IListenerServiceCallback request)
        {
            ProcessEntity processConfig = null;
            InteligenceModuleEntity imConfig = null;
            try
            {
                _resourceAccess.RetrieveInteligenceModuleAndProcessConfiguration(processId, out processConfig, out imConfig);

                if (processConfig.InstanceContext == PSE.ProcessManager.Entity.Enums.InstanceContext.Single)
                {

                    //Verifica se o dicionário contém a chave da processo.
                    if (_connectedClients.ContainsKey(processId))
                    {
                        //Se não houver uma entrada para este request, adiciona ele no dicionario.
                        if (!_connectedClients[processId].Contains(request))
                            _connectedClients[processId].Add(request);
                    }
                    else
                        _connectedClients.Add(processId, new List<IListenerServiceCallback> { request });
                }
                else
                {
                    MultipleProcessKey key = new MultipleProcessKey();
                    key.ClientId = clientIdentifier;
                    key.ProcessId = processId;
                    //Verifica se o dicionário contém a chave do identificador do Cliente.
                    if (!_connectedClientsToMultipleProcess.ContainsKey(key))
                        _connectedClientsToMultipleProcess.Add(key, request);
                }

                //TODO: Apenas deve mandar a mensagem de sucesso de conexão quando 
                //for uma chamada assíncrona.
                //SendMessageSucessfullConnect(request);
            }
            catch (Exception ex)
            {
                HandleException(processId, new Exception("Error sending message to client.", ex));
            }
        }

        /// <summary>
        /// Evento disparado ao receber uma solicitação de desconexão do Client
        /// </summary>
        /// <param name="request">Channel do Client</param>
        void InteligenceManager_OnDisconnect(IListenerServiceCallback request)
        {
            try
            {
                foreach (KeyValuePair<int, List<IListenerServiceCallback>> client in _connectedClients)
                {
                    if (client.Value.Contains(request))
                        client.Value.Remove(request);
                }

                MultipleProcessKey key = new MultipleProcessKey();
                foreach (KeyValuePair<MultipleProcessKey, IListenerServiceCallback> client in _connectedClientsToMultipleProcess)
                {
                    if (client.Value == request)
                    {
                        key = client.Key;
                        break;
                    }
                }

                if (!String.IsNullOrEmpty(key.ClientId))
                    _connectedClientsToMultipleProcess.Remove(key);
            }
            catch (Exception ex)
            {
                HandleException(ex);
            }
        }

        #region SendMessageSucessfullConnect

        /// <summary>
        /// Informa que a conexão foi efetuada com sucesso.
        /// </summary>
        /// <param name="request"></param>
        private void SendMessageSucessfullConnect(IListenerServiceCallback request)
        {
            Message message = new Message();
            message.MessageId = _sucessOnConectMessageId;
            request.BeginSendMessageCallBack(
                MessageCreator.CreateMessage(message, ListenerService.Actions.SendMessageCallBack), new AsyncCallback(AssyncResult), request);
        }
        /// <summary>
        /// Delegate de retorno da chamada assíncrona.
        /// </summary>
        /// <param name="result"></param>
        private void AssyncResult(IAsyncResult result)
        {
            try
            {
                IListenerServiceCallback request = (IListenerServiceCallback)result.AsyncState;
                request.EndSendMessageCallBack(result);
            }
            catch (Exception ex)
            {
                ErrorLogger.WriteLog(LogType.Error, new Exception("An error ocurred sending message.", ex));
            }
        }

        #endregion

        #endregion

        #region ReceiveMessageFromClient

        /// <summary>
        /// Evento disparado ao receber uma mensagem do Client.
        /// </summary>
        /// <param name="message"></param>
        void InteligenceManager_OnReceivedMessage(BaseMessage message)
        {
            // Se for mensagem de abort, stop ou close e processo não estiver iniciado não faz nada
            if ((message.Operation == "Abort" || 
                 message.Operation == "Stop" || 
                 message.Operation == "Close") &&
                !InteligenceManagerInstanceProcessManager.GetInstance().IsChildStarted(message.ProcessId))
            {
                return;
            }

            BaseInteligenceModule inteligenceModule = null;

            try
            {
                //Recupera o InteligenceModule associado a mensagem.
                inteligenceModule = _inteligenceModuleLoader.LoadAndInitializeInteligenceModule(message.ProcessId, message.From);

                if (inteligenceModule == null)
                    return;

                if (inteligenceModule.OnReceiveMessage == null)
                    //Define o evento que será disparado ao receber mensagens deste inteligenceModule.
                    inteligenceModule.OnReceiveMessage = new ServiceEvents.MessageReceived(OnReceivedMessageFromInteligenceModule);
            }
            catch (Exception ex)
            {
                HandleException(message.ProcessId, new Exception("Error loading InteligenceModule", ex));
                return;
            }

            if (message.Operation == "Close")
            {
                InteligenceManagerInstanceProcessManager.GetInstance().CloseChild(message.ProcessId);
            }
            else if (!string.IsNullOrEmpty(message.Operation))
            {
                try
                {
                    //Envia a mensagem para o Inteligence Module.
                    inteligenceModule.Execute(message);
                }
                catch (Exception ex)
                {
                    HandleException(message.ProcessId, new Exception("Could not process message. See inner exceptions for more details.", ex));
                } 
            }
        }

        #endregion

        #region ReceiveMessageFromInteligenceModule

        void OnReceivedMessageFromInteligenceModule(BaseMessage message)
        {
            //Verifica se é uma mensagem de final de execução.
            if (message.MessageId == _processFinalizedMessageId || message.MessageId == _processFinalizedOnFailMessageId)
                ProcessEnded(message);
            //Caso não seja uma mensagem para finalizar um determinado processo, então é uma mensagem de Callback.
            HandleMessageReceivedFromInteligenceModule(message);
        }

        private void HandleMessageReceivedFromInteligenceModule(BaseMessage message)
        {
            //Percorre todos os clients conectados com este processo.
            List<IListenerServiceCallback> clients = null;
            _connectedClients.TryGetValue(message.ProcessId, out clients);

            if (clients != null)
            {
                foreach (IListenerServiceCallback callback in clients)
                {
                    if ((callback as ICommunicationObject).State == CommunicationState.Opened)
                        callback.BeginSendMessageCallBack(
                            MessageCreator.CreateMessage(message, ListenerService.Actions.SendMessageCallBack), new AsyncCallback(AssyncResult), callback);
                }
            }

            IListenerServiceCallback client = null;

            MultipleProcessKey key = new MultipleProcessKey();
            key.ProcessId = message.ProcessId;
            key.ClientId = message.To;

            foreach (KeyValuePair<MultipleProcessKey, IListenerServiceCallback> request in _connectedClientsToMultipleProcess)
            {
                if (request.Key.ClientId == message.To)
                {
                    client = request.Value;
                    break;
                }
            }

            if (client != null)
            {
                if ((client as ICommunicationObject).State == CommunicationState.Opened)
                {
                    client.BeginSendMessageCallBack(
                        MessageCreator.CreateMessage(message, ListenerService.Actions.SendMessageCallBack), new AsyncCallback(AssyncResult), client);
                }
            }
        }

        #endregion

        /// <summary>
        /// Método executado ao receber uma mensagem de Processo Finalizado.
        /// </summary>
        /// <param name="message"></param>
        private void ProcessEnded(BaseMessage message)
        {
            //Verifica se o processo foi finalizado por erro.
            if (message.MessageId == _processFinalizedOnFailMessageId)
            {
                BaseInteligenceModule inteligenceModule = _inteligenceModuleLoader.RetrieveInteligenceModuleFromSingleCache(message.ProcessId);

                //if (inteligenceModule != null)
                //    inteligenceModule.HandleProcessOnFail();

            }
            else
            {
                _inteligenceModuleLoader.RemoveInteligenceModuleFromCache(message.ProcessId, message.To);
            }
        }

        #region SendMessageToClient
        /// <summary>
        /// Envia a mensagem para os Clientes conectados ao processo.
        /// </summary>
        /// <param name="message"></param>
        private void SendMessageToClient(BaseMessage message)
        {
            //Percorre todos os clients conectados com este processo.
            List<IListenerServiceCallback> clients = null;
            _connectedClients.TryGetValue(message.ProcessId, out clients);

            if (clients != null)
                foreach (IListenerServiceCallback callback in clients)
                {
                    try
                    {
                        callback.BeginSendMessageCallBack(
                            MessageCreator.CreateMessage(message, ListenerService.Actions.SendMessageCallBack), new AsyncCallback(AssyncResult), callback);
                    }
                    catch (Exception ex)
                    {
                        HandleException(new Exception("Could not send message to Client.", ex));
                    }
                }
        }
        #endregion

        #region Controles Internos e Configurações.

        #region HandleException
        private void HandleException(Exception ex)
        {
            ErrorLogger.WriteLog(LogType.Error, ex);
        }

        private void HandleException(int processId, Exception ex)
        {
            this.HandleException(ex);

            Message<string> message = new Message<string>();
            message.MessageId = _errorMessageId;
            message.ProcessId = processId;
            message.Parameter = ex.Message;
            //message.OperationParameters.Add("ExceptionMessage", Logger.FormatExceptionMessage(exception));
            SendMessageToClient(message);
        }
        #endregion

        //#region RetrieveInteligenceManagerServicePort

        ///// <summary>
        ///// Busca nas configurações a porta que será utilizada para o listener.
        ///// </summary>
        ///// <returns></returns>
        //private int RetrieveInteligenceManagerServicePort()
        //{
        //    //Recupera a porta do serviço do Inteligence Manager.
        //    string port = System.Configuration.ConfigurationManager.AppSettings[_inteligenceManagerServicePortKey];
        //    int iport = 0;
        //    if (int.TryParse(port, out iport))
        //        return iport;
        //    else
        //        throw new ConfigurationErrorsException("The port provided in configuration file is not valid.");
        //}

        //#endregion

        #region Dispose

        /// <summary>
        /// Finaliza os gateways conectados.
        /// </summary>
        public void Dispose()
        {
            _inteligenceModuleLoader.Dispose();
            _listenerGateway.Dispose();
        }

        #endregion

        protected override SystemComponent GetSystem()
        {
            return PSE.Framework.Configuration.Components.SystemComponents.System;
        }

        protected override SystemComponent GetComponent()
        {
            return PSE.Framework.Configuration.Components.SystemComponents.ProcessManagerInstance;
        }

        #endregion
    }
}
